/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, AstTreeUtils, ts } from 'arkanalyzer';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import { Rule, Defects, MatcherTypes, MatcherCallback, FileMatcher } from '../../Index';
import { RuleListUtil } from '../../utils/common/DefectsList';
import { IssueReport } from '../../model/Defects';

interface Violation {
    line: number;
    character: number;
    message: string;
    suggestion: string;
    filePath?: string;
    node: ts.Node
};

export class NoRegexSpacesCheck implements BaseChecker {
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    public rule: Rule;
    public metaData: BaseMetaData = {
        severity: 2,
        ruleDocPath: 'docs/no-regex-spaces-check.md',
        description: 'Disallow unintended fallthrough cases in switch statements.',
    };

    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE
    };

    public registerMatchers(): MatcherCallback[] {
        const fileMatcher: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        };
        return [fileMatcher];
    };

    public check = (target: ArkFile): void => {
        const code = target.getCode();
        const astRoot = AstTreeUtils.getASTNode(target.getName(), code);
        const violations = this.checkAction(astRoot, astRoot);
        violations.forEach((violation) => {
            const filePath = target.getFilePath();
            let defect = this.addIssueReport(violation, filePath);
            let ruleFix = this.createFix(violation.node, violation.suggestion);
            this.issues.push(new IssueReport(defect, ruleFix));
        });
    };

    private checkAction(astRoot: ts.Node, sourceFile: ts.SourceFile): Violation[] {
        const violations: Violation[] = [];
        const checkNode = (node: ts.Node): void => {
            // 检查正则表达式字面量
            if (ts.isRegularExpressionLiteral(node)) {
                this.checkRegexLiteral(node, sourceFile, violations);
            }
            // 检查通过 RegExp 构造函数创建的正则表达式
            if (
                (ts.isNewExpression(node) || ts.isCallExpression(node)) &&
                ts.isIdentifier(node.expression) &&
                node.expression.text === 'RegExp'
            ) {
                this.checkRegExpConstructor(node, sourceFile, violations, node);
            }
            ts.forEachChild(node, checkNode);
        };

        checkNode(astRoot);
        return violations;
    };

    private checkRegexLiteral(node: ts.RegularExpressionLiteral, sourceFile: ts.SourceFile, violations: Violation[]): void {
        const regexText = node.text;
        if (!/ {2,}/.test(regexText) || /^\s+$/.test(regexText)) {
            return;
        }
        const hasMultipleSpaces = this.checkForMultipleSpaces(regexText.replace(/\t/g, ''));
        if (hasMultipleSpaces) {
            const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
            const suggestion = this.fixMultipleSpaces(regexText);
            violations.push({
                message: `Multiple spaces found in regular expression.`,
                line: line + 1,
                character: character + 1,
                suggestion: suggestion,
                node: node
            });
        }
    };

    private checkRegExpConstructor(
        node: ts.NewExpression | ts.CallExpression,
        sourceFile: ts.SourceFile,
        violations: Violation[], parentNode: ts.Node): void {
        if (node.arguments && node.arguments.length > 0 && ts.isStringLiteral(node.arguments[0])) {
            const regexText = node.arguments[0].text;
            if (!/ {2,}/.test(regexText) || /^\s+$/.test(regexText)) {
                return;
            }
            // 查找并报告多个连续空格
            const spacesPattern = /(?<!\\)( {2,})(?![\[\]])/gu;
            let match: RegExpExecArray | null;
            while ((match = spacesPattern.exec(regexText))) {
                const { index, 1: spaces } = match;
                const length = spaces.length; // 连续空格的数量
                // 检查这些空格是否在字符类或量词内
                if (this.isInsideCharacterClass(index, regexText) || this.isInsideQuantifier(index, regexText)) {
                    continue; // 如果在字符类或量词内，则跳过
                }
                const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
                const suggestion = this.fixMultipleSpacess(regexText);
                violations.push({
                    message: `Multiple spaces found in regular expression created by RegExp constructor {${length}}.`,
                    line: line + 1,
                    character: character + 1,
                    suggestion: suggestion,
                    node: node
                });
                break;
            }
        }
    };

    // 检查某个位置是否在量词 { } 内
    private isInsideQuantifier(position: number, pattern: string): boolean {
        let inQuantifier = false;
        let escape = false;
        for (let i = 0; i < pattern.length; i++) {
            if (escape) {
                escape = false;
                continue;
            }
            if (pattern[i] === '\\') {
                escape = true;
                continue;
            }
            if (pattern[i] === '{') {
                inQuantifier = true;
            } else if (pattern[i] === '}' && inQuantifier) {
                inQuantifier = false;
            }
            if (i === position) {
                return inQuantifier;
            }
        }
        return false;
    };

    private isInsideCharacterClass(position: number, pattern: string): boolean {
        let inCharacterClass = false;
        let escape = false;
        for (let i = 0; i < pattern.length; i++) {
            if (escape) {
                escape = false;
                continue;
            }
            if (pattern[i] === '\\') {
                escape = true;
                continue;
            }
            if (pattern[i] === '[') {
                inCharacterClass = true;
            } else if (pattern[i] === ']' && inCharacterClass) {
                inCharacterClass = false;
            }
            if (i === position) {
                return inCharacterClass;
            }
        }
        return false;
    };

    private checkForMultipleSpaces(regexText: string): boolean {
        if (this.isRegexOnlyWhitespace(regexText)) {
            return false;
        }
        const cleanedRegex = this.cleanCharacterSets(regexText);
        const cleanedRegexWithEscapedSpaces = this.replaceEscapedSpaces(cleanedRegex);
        return this.checkForMultipleSpacesInCleanedRegex(cleanedRegexWithEscapedSpaces);
    };

    private isRegexOnlyWhitespace(regexText: string): boolean {
        return /^\s+$/.test(regexText);
    };

    private cleanCharacterSets(regexText: string): string {
        const charSetRegex = /\[[^\[\]]*\]/g;
        return regexText.replace(charSetRegex, (match) => {
            return this.processCharacterSet(match);
        });
    };

    private processCharacterSet(match: string): string {
        if (match.includes('\\')) {
            return match; // 保留原样
        } else {
            return this.reduceSpacesInMatch(match);
        }
    };

    private reduceSpacesInMatch(match: string): string {
        let reducedMatch = match;
        while (/\s{2,}/.test(reducedMatch)) {
            reducedMatch = reducedMatch.replace(/\s{2,}/g, ' ');
        }
        return reducedMatch;
    };

    private replaceEscapedSpaces(cleanedRegex: string): string {
        return cleanedRegex.replace(/\\(\s{2,})/g, '\\{$1.length}');
    };

    private quantifierWithSpaces = /(\s+)(?=[\+\*\?{])/;

    private checkForMultipleSpacesInCleanedRegex(cleanedRegex: string): boolean {
        const match = this.quantifierWithSpaces.exec(cleanedRegex);
        if (match) {
            return this.evaluateQuantifierMatch(match);
        }
        return this.checkForGeneralMultipleSpaces(cleanedRegex);
    };

    private evaluateQuantifierMatch(match: RegExpExecArray): boolean {
        return match[1].length >= 3;
    };

    private checkForGeneralMultipleSpaces(cleanedRegex: string): boolean {
        return /(?<!\\)\s{2,}/.test(cleanedRegex);
    };

    private MultipleSpacess = /(\s+)(?=[\+\*\?{])/;

    private fixMultipleSpacess(regexText: string): string {
        // 预处理：将转义的空格替换为占位符
        regexText = regexText.replace(/\\/g, '\\\\');
        const placeholder = '\\{1}';
        let fixedRegex = regexText.replace(/\\ /g, placeholder);
        fixedRegex = fixedRegex.replace(/(?<!\\)\s{2,}/g, match => {
            const matchs = this.MultipleSpacess.exec(fixedRegex);
            if (matchs) {
                const spaceCount = match.length - 1;
                if (spaceCount > 0) {
                    return ` {${spaceCount}} `;
                }
                return '';
            }
            const spaceCount = match.length;
            return ` {${spaceCount}}`;
        });
        // 恢复占位符为原来的转义空格
        fixedRegex = fixedRegex.replace(new RegExp(placeholder, 'g'), '\\ ');
        return fixedRegex;
    };

    private fixMultipleReplace = /\[[^\]]*\]/g;

    private fixMultipleSpaces(regexText: string): string {
        // 预处理：将转义的空格替换为占位符
        regexText = regexText.replace(/\\/g, '\\\\');
        const placeholder = '\\{1}';
        let fixedRegex = regexText.replace(/\\ /g, placeholder);
        // 修复字符集内的空格
        fixedRegex = fixedRegex.replace(this.fixMultipleReplace, match => {
            // 逐步减少字符集内的空格数量
            let reducedMatch = match;
            while (/\s{2,}/.test(reducedMatch)) {
                reducedMatch = reducedMatch.replace(/\s{2,}/g, ' ');
            }
            return reducedMatch;
        });
        // 修复字符集外的多余空格
        this.fix(fixedRegex);
        // 恢复占位符为原来的转义空格
        fixedRegex = fixedRegex.replace(new RegExp(placeholder, 'g'), '\\ ');
        return fixedRegex;
    };

    private quantifierWith = /(\s+)(?=[\+\*\?{])/;

    private fixReplace = /(?<!\\)\s{2,}/g;

    private fix(fixedRegex: string): string {
        // 修复字符集外的多余空格
        fixedRegex = fixedRegex.replace(this.fixReplace, match => {
            const matchs = this.quantifierWith.exec(fixedRegex);
            if (matchs) {
                const spaceCount = match.length - 1;
                if (spaceCount > 0) {
                    return ` {${spaceCount}} `;
                }
                return '';
            }
            const spaceCount = match.length;
            return ` {${spaceCount}}`;
        });
        return fixedRegex;
    }

    private createFix(node: ts.Node, fixText: string): { range: [number, number], text: string } {
        return {
            range: [node.getStart(), node.getEnd()],
            text: fixText
        };
    };

    private addIssueReport(violation: Violation, filePath: string): Defects {
        this.metaData.description = violation.message;
        const severity = this.rule.alert ?? this.metaData.severity;
        let defect = new Defects(
            violation.line,
            violation.character,
            violation.character,
            this.metaData.description,
            severity, this.rule.ruleId,
            filePath,
            this.metaData.ruleDocPath, true, false, false);
        this.defects.push(defect);
        RuleListUtil.push(defect);
        return defect;
    };
}